En esta práctica vamos a realizar un estudio del archivo "airbnb-listings-extract.csv". Este documento recoge diferentes datos sobre un listado de alojamientos de Airbnb. Para realizar su estudio vamos a seguir diferentes etapas:
Carga de librerías y datos: En esta etapa, se importan las librerías necesarias para el análisis de datos y se cargan los datos del archivo CSV. Esto nos permite tener acceso a las herramientas y funciones necesarias para manipular y analizar los datos.
Preprocesado de los datos y análisis de datos nulos: En esta etapa, se realiza un preprocesamiento de los datos para asegurarse de que estén limpios y preparados para el análisis. Esto implica tratar los datos nulos o faltantes, eliminar columnas no útiles, corregir formatos de datos incorrectos, entre otros pasos. El análisis de los datos nulos nos ayuda a comprender qué variables tienen valores faltantes y cómo tratarlos.
Análisis descriptivo de las variables: En esta etapa, se realiza un análisis estadístico y descriptivo de las variables del conjunto de datos. Esto incluye calcular medidas de tendencia central, dispersión, distribución y relaciones entre variables. Proporciona una visión general de los datos y ayuda a identificar patrones, tendencias y características importantes.
Creación de dummies: En esta etapa, se crean variables dummy o variables ficticias para las variables categóricas. Esto implica convertir las variables categóricas en una representación numérica que pueda ser utilizada por los algoritmos de aprendizaje automático. Ayuda a incorporar información categórica en el análisis sin asignar un orden numérico arbitrario.
División del dataframe en train y test: En esta etapa, se divide el conjunto de datos en conjuntos de entrenamiento y prueba. El conjunto de entrenamiento se utiliza para ajustar y entrenar los modelos, mientras que el conjunto de prueba se utiliza para evaluar el rendimiento y la capacidad de generalización de los modelos.
Aplicación de distintos algoritmos: En esta etapa, se aplican diferentes algoritmos de aprendizaje automático para realizar predicciones o modelar relaciones entre variables.
6.1 Regresión lineal: La regresión lineal es un método utilizado para modelar la relación entre una variable dependiente y una o más variables independientes. Puede ayudar a identificar y cuantificar la influencia de las variables independientes en la variable dependiente. En nuestro estudio, la regresión lineal puede proporcionar información sobre cómo las diferentes variables pueden estar relacionadas con el precio de los alojamientos de Airbnb y ayudar a predecir el precio en función de esas variables.
6.2 Árboles de decisión: Los árboles de decisión son modelos que utilizan estructuras de árbol para tomar decisiones basadas en múltiples condiciones o características. Pueden ser útiles para identificar patrones y segmentar los datos en función de diferentes características. En nuestro estudio, los árboles de decisión pueden ayudar a identificar las características más importantes que influyen en el precio de los alojamientos y proporcionar una forma interpretable de comprender cómo se toman las decisiones en función de esas características.
6.3 K-NN: K-NN es un algoritmo que se utiliza para clasificar o predecir nuevos puntos de datos en función de su similitud con los puntos de datos existentes. Funciona encontrando los k puntos de datos más cercanos a un nuevo punto y tomando su clase o valor promedio. En nuestro estudio, K-NN puede ayudar a identificar los alojamientos similares en términos de características y precios, y realizar predicciones basadas en alojamientos comparables.
6.4 Gradient Boosting: El Gradient Boosting es una técnica de ensamblaje de modelos que combina múltiples modelos de aprendizaje débiles para crear un modelo más fuerte. Se enfoca en minimizar el error residual en cada iteración y ajusta los pesos de los modelos individuales. En nuestro estudio, el Gradient Boosting puede mejorar la precisión de las predicciones del precio de los alojamientos de Airbnb al combinar varios modelos y aprovechar sus fortalezas individuales.
6.5 Bagging: Bagging es una técnica de ensamblaje que combina múltiples modelos de aprendizaje independientes entre sí. Cada modelo se entrena en una muestra aleatoria del conjunto de datos y luego se promedian las predicciones para tomar la decisión final. En nuestro estudio, el Bagging puede reducir la variabilidad y mejorar la estabilidad de las predicciones al considerar diferentes perspectivas de modelos.
6.6 Boosting: El Boosting es otra técnica de ensamblaje que se basa en la secuencialidad de los modelos. Cada modelo se entrena en función de los errores cometidos por el modelo anterior, y se da más importancia a los datos mal clasificados o mal predichos. En nuestro estudio, el Boosting puede ayudar a mejorar gradualmente las predicciones de los alojamientos de Airbnb al enfocarse en los errores y ajustar los modelos subsiguientes en consecuencia.
Clustering: En esta etapa, se realiza el análisis de clustering para identificar grupos o clusters naturales en los datos. Esto ayuda a encontrar patrones o segmentos similares en los datos sin la necesidad de etiquetas predefinidas. Puede proporcionar información sobre la estructura subyacente de los datos y ayudar en la segmentación y comprensión de los grupos.
En primer lugar, vamos a empezar por cargar todas las librerías que creamos que son necesarias para realizar la práctica.
import numpy as np
import pandas as pd
from datetime import datetime
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
%matplotlib inline
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import StandardScaler
from sklearn import linear_model
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.metrics import r2_score, mean_absolute_error
import plotly.graph_objects as go
from sklearn.ensemble import BaggingRegressor
from sklearn.metrics import mean_absolute_error
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.cluster import KMeans, AgglomerativeClustering, Birch, SpectralClustering
from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN
from sklearn.metrics import silhouette_score
from scipy import stats
# pip install folium
import folium
from folium.plugins import MarkerCluster
La siguiente celda nos permite saber el directorio en el que se encuentra el archivo.
import os
# Obtener el directorio actual
current_dir = os.getcwd()
# Imprimir el directorio actual
print("El directorio actual es:", current_dir)
Esta celda sirve exclusivamente para cargar los datos en el ordenador de una de las alumnas que realizan la práctica, ya que cada una tiene el archivo guardado en diferentes sitios.
from google.colab import drive drive.mount('/content/drive') df = pd.read_csv("/content/drive/MyDrive/Master_4º/airbnb-listings-extract.csv", sep=';', decimal='.') df.head()
Esta otra celda es una vez más la carga de datos, en el caso de no utilizar la celda previamente mostrada.
df = pd.read_csv('airbnb-listings-extract.csv', sep=';', decimal='.')
df.head()
En primer lugar vamos a eliminar columnas que no resultan útiles para nuestro análisis. Finalmente tendremos un dataframe que consta de 27 columnas.
not_useful_columns = [
'ID', 'Listing Url', 'Scrape ID', 'Last Scraped', 'Thumbnail Url', 'Medium Url',
'Picture Url', 'XL Picture Url', 'Host ID', 'Host URL', 'Host Name',
'Host Thumbnail Url', 'Host Picture Url', 'Host Neighbourhood', 'Weekly Price',
'Monthly Price', 'Calendar Updated', 'Calendar last Scraped', 'First Review',
'Last Review', 'Reviews per Month', 'Geolocation', 'Calculated host listings count',
'Host Listings Count', 'Host Total Listings Count'
]
somehow_useful_columns_but_droppable_anyway = ['Name', 'Summary', 'Space', 'Description',
'Neighborhood Overview', 'Notes', 'Transit', 'Access', 'Interaction', 'House Rules',
'Host Location', 'Host About'
]
columns_with_too_many_missing_values = [
'Host Acceptance Rate', 'Square Feet', 'Has Availability', 'License', 'Jurisdiction Names'
]
df.drop(not_useful_columns, axis=1, inplace=True)
df.drop(somehow_useful_columns_but_droppable_anyway, axis=1, inplace=True)
df.drop(['Street', 'State', 'Market', 'Smart Location', 'Country', 'Zipcode'], axis=1, inplace=True)
df.drop(columns_with_too_many_missing_values, axis=1, inplace=True)
reviews = df.loc[:, "Review Scores Rating":"Review Scores Value"]
df['Average Review'] = reviews.mean(axis=1)
df.drop(['Availability 30', 'Availability 60', 'Availability 90', 'Review Scores Rating',
'Review Scores Accuracy', 'Review Scores Cleanliness', 'Review Scores Checkin',
'Review Scores Communication', 'Review Scores Location', 'Review Scores Value',
'Accommodates'], axis=1, inplace=True)
df = df[df['Price'] < 200]
df["Final Neighbourhood"] = df["Neighbourhood Group Cleansed"].fillna(df["Neighbourhood Cleansed"])
df.drop(["Neighbourhood", "Neighbourhood Group Cleansed", "Neighbourhood Cleansed"], axis=1, inplace=True)
df['Features'].fillna("", inplace=True) # rellenamos los huecos con cadenas vacías
df['Amenities'].fillna("", inplace=True)
df['Host Verifications'].fillna("", inplace=True)
df['Features'] = df['Features'].apply(lambda x: len(str(x).split(',')))
df['Amenities'] = df['Amenities'].apply(lambda x: len(str(x).split(',')))
df['Host Verifications'] = df['Host Verifications'].apply(lambda x: len(str(x).split(',')))
df['Host Since'] = pd.to_datetime(df['Host Since'], errors='coerce')
df['Years Being Host'] = np.where(df['Host Since'].isnull(), np.nan, 2017 - df['Host Since'].dt.year)
df.drop(['Host Since'], axis=1, inplace=True)
df["Host Response Time"].fillna(df["Host Response Time"].mode()[0], inplace=True)
df.drop(['Experiences Offered'], axis=1, inplace=True)
df.drop(['Country Code'], axis=1, inplace=True)
print(df.shape)
df.head()
La función definida a continuación, remove_entries, cuenta y elimina los elementos de una columna del DataFrame que tienen menos de n registros, y luego muestra un gráfico de barras con el número de registros por elemento. También nos muestra el número de registros eliminados y el número de elementos distintos restantes.
from collections import Counter
def remove_entries(df, n, col, element_name):
nb_counts = Counter(df[col])
inicial = df.shape[0]
# Borramos elementos con menos de n registros
for i in list(nb_counts):
if nb_counts[i] < n:
del nb_counts[i]
df = df[df[col] != i]
# Dibujamos
tdf = pd.DataFrame.from_dict(nb_counts, orient='index').sort_values(by=0)
ax = tdf.plot(kind='bar', figsize = (22,4), color = '#007A87', alpha = 0.85)
ax.set_title(f"{element_name} por número de registros")
ax.set_xlabel(f"{element_name}")
ax.set_ylabel("# de registros")
plt.show()
print('Número de registros eliminado: ', inicial - df.shape[0])
print(f"Número de {element_name}s:", len(nb_counts))
return df
df = remove_entries(df, 200, "Final Neighbourhood", "Barrio")
Hemos seleccionado para nuestro análisis aquellos valores cuyo barrio se encuentra en Madrid (Puente de Vallecas, Ciudad Lineal, Chamartín, Carabanchel, Moncloa-Aravaca, Retiro, Tetúan, Arganzuela, Salamanca, Chamberí o el Centro).
df.columns
La función describe() la utilizamos para calcular y mostrar estadísticas descriptivas de los datos.
Cuando se aplica la función describe() a un DataFrame, proporciona un resumen estadístico de cada columna numérica en el DataFrame, que incluye:
df.describe()
En la siguiente celda podemos ver los diferentes datos mostrados en la columna "Property Type".
df['Property Type'].unique()
pd.set_option('display.max_colwidth', 0)
df[['Property Type', 'Room Type', 'Bed Type', 'Guests Included', 'Cancellation Policy', 'Host Response Rate']].agg(['unique']).transpose()
En primer lugar vamos a calcular la cantidad de valores nulos que encontramos para cada una de las columnas, para posteriormente tratarlos. En la columna total vemos el recuento de valores NA y en la columna Percent aparece como porcentaje en relación con todos los valores que contiene dicha variable.
total= df.isnull().sum().sort_values(ascending=False)
percent= (df.isnull().sum()/df.isnull().count()).sort_values(ascending=False)*100
missing_data= pd.concat([total, percent],axis=1, keys=["Total", "Percent"])
missing_data.head(50)
Observamos como la mayoría de variables no tienen valores NA, sin embargo hay 9 columnas que sí y debemos modificarlos para continuar. La columna Security Deposit tiene un mayor porcentaje de valores nulos que de valores correctos; y en el caso de Cleaning Fee, Average Review y Host Response Rate, este porcentaje también es elevado.
En las columnas Average Review, Security Deposit, Cleaning Fee, Years Being Host y Host Response Rate vamos a reemplazar los valores NA por un '0'. Ya que ese valor nulo se debe a que quizás nunca han recibido un review porque no se ha alquilado nunca, o porque los anfitriones deciden no cobrar por limpieza.
df['Average Review'].fillna(0, inplace=True)
df['Security Deposit'].fillna('0.00', inplace=True)
df['Cleaning Fee'].fillna('0.00', inplace=True)
df['Years Being Host'].fillna(0, inplace=True)
df['Host Response Rate'].fillna(0, inplace=True)
Además vamos a eliminar de dataframe los valores NA de las columnas Bathrooms, Bedrooms y Beds porque no aportan información relevante.
df.dropna(subset=['Bathrooms', 'Bedrooms', 'Beds'], inplace=True)
total= df.isnull().sum().sort_values(ascending=False)
percent= (df.isnull().sum()/df.isnull().count()).sort_values(ascending=False)*100
missing_data= pd.concat([total, percent],axis=1, keys=["Total", "Percent"])
missing_data.head(50)
Observamos como el número de NAs se ha reducido tras la intervención.
A continuación vamos a comenzar con el análisis descriptivo de las variables. Es especialmente importante en esta práctica ya que estamos tratando de construir un generador de precios que tomará en cuenta todas las variables y las relaciones y diferencias que observemos entre ellas, para generar un precio final para cada propiedad.
room_type_plot = sns.countplot(x="Room Type", order = df['Room Type'].value_counts().index, data=df)
room_type_plot.set(xlabel='Room Type', ylabel='', title='Room Type Count Bar')
for bar in room_type_plot.patches:
h = bar.get_height()
room_type_plot.text(
bar.get_x() + bar.get_width()/2., # bar index (x coordinate of text)
h, # y coordinate of text
'%d' % int(h), # y label
ha='center',
va='bottom',
color='black',
fontweight='bold',
size=14)
plt.show()
En primer lugar vamos a analizar la variable Room Type, que tiene 3 valores distintos. Para el valor 'Entire home/apt' tenemos un total de 7269 opciones en Madrid, seguidamente tenemos 4502 para 'Private room' y por último tan solo 150 para el tipo 'Shared room'. La mayoría de clientes tienden a buscar un apartamento completo o una habitación privada por lo que no es especialmente relevante que el número de habitaciones compartidas sea bajo.
A continuación analizaremos la ubicación de las propiedades basándonos en sus referecias de coordenadas por latitud y longitud.
coordenadas = df[['Latitude', 'Longitude']].copy()
coordenadas
# Crear un mapa centrado en Madrid
madrid_map = folium.Map(location=[40.4168, -3.7038], zoom_start=12)
# Crear un grupo de marcadores
marker_cluster = MarkerCluster().add_to(madrid_map)
# Añadir marcadores para cada punto de ubicación de Airbnb al grupo de marcadores
for index, row in df.iterrows():
folium.Marker([row['Latitude'], row['Longitude']]).add_to(marker_cluster)
# Mostrar el mapa
madrid_map
En el mapa que tenemos arriba podemos observar mediante puntos naranjas aquellas zonas con una mayor aglutinación de propiedades, en amarillo aparecen zonas que no superan los 100 Airbnbs y en verde aquellas que no superan los 10 Airbnbs. Si hacemos zoom sobre el mapa podemos observar los distintos barrios que encontramos en nuestro dataframe. El barrio de Madrid Centro es el que tiene un mayor número de Airbnbs, con un total de 7164, a continuación encontramos el barrio de Retiro con 1206 y en tercer lugar el barrio de Salamanca con 1120. A simple vista podemos observar como la mayoría se encuentran en la zona más céntrica de Madrid ciudad, sin embargo también encontramos un número elevado de propiedades en la zona de Madrid Norte y en la Latina y al sureste de la ciudad. Las zonas donde menos Airbnbs hay son en Pozuelo y Majadahonda, zonas también más alejadas del centro de la ciduad.
import matplotlib.pyplot as plt
# Crear un gráfico de dispersión con los puntos de Airbnb
plt.scatter(df['Longitude'], df['Latitude'], s=5, alpha=0.5)
# Configurar el título y etiquetas de los ejes
plt.title("Ubicaciones de Airbnb en Madrid")
plt.xlabel("Longitud")
plt.ylabel("Latitud")
# Mostrar el gráfico
plt.show()
Seguidamente vamos a analizar la distribución de los precios. Estos es previsible que varíen fuertemente en función de la zona en la que se encuentren, además de su tamaño y características. En primer lugar utilizaremos la función describe() para hacer un análisis inicial de sus valores, tenemos un total de 11921 propiedades con una media de precio de 60 euros la noche, un mínimo precio de 9 euros y un máximo de 199 euros.
df['Price'].describe()
El siguiente Boxplot nos permitirá detectar posibles valores atípicos que puedan distorsionar gravemente nuestra muestra de estudio.
box_plot = dict(markerfacecolor='r', markeredgecolor='r', marker='.')
price_dist = df['Price'].plot(kind='box', vert=False, flierprops=box_plot, title = "Price", figsize=(16,2));
plt.title('Price\n', fontsize=14, fontweight='bold')
price_dist
No encontramos un número suficientemente alto de outliers que pudieran afectar al análisis distorsionando los resultados, por lo que optamos por no eliminar ningun valor. Además cabe destacar que la mayoría de precios se sitúan en torno a los 30-80 euros por noche y únicamente unos cuantos superan los 150 euros por noche.
En este apartado vamos a analizar los distintos tipos de propiedad.
property_df = df['Property Type'].value_counts()
property_df.columns = ['property_type']
property_df.plot.bar(y= 'property_type',
color = 'LightBlue',
fontsize = 12,
legend = False,
figsize= (6, 4))
plt.title('Property Type Distribution\n', fontsize=14, fontweight='bold')
df['Property Type'].value_counts()
En la ciudad de Madrid hay un total de 9957 apartamentos ofertados por Airbnb, seguidamente 813 casas y 301 condominios. Es curioso resaltar que en Airbnb solo aparecen listados uno de las siguientes categorías: una habitación Guest Suite, un Bungalow, un Timeshare (que consiste en un tipo de alojamiento con un régimen de tiempo compartido para su uso) y un Carpa.
len(df['Property Type'].value_counts())
Debido a que encontramos 21 tipos de propiedad, vamos a reducirlas creado 5 nuevas categorias: apartamento, casa, hotel1, hotel2 y otros.
dict1 = {'Apartment':['Condominium','Timeshare','Loft','Serviced apartment','Guest suite'],
'House':['Vacation home','Villa','Townhouse','In-law','Casa particular', 'Chalet'],
'Hotel1':['Dorm','Hostel','Guesthouse'],
'Hotel2':['Boutique hotel','Bed & Breakfast'],
'Other':['Island','Castle','Yurt','Hut','Treehouse',
'Earth House','Tipi','Cave','Train','Parking Space','Lighthouse',
'Tent','Boat','Cabin','Camper/RV','Bungalow']
}
dict2 = {i : k for k, v in dict1.items() for i in v}
df['Property Type'] = df['Property Type'].replace(dict2)
df['Property Type'].replace(dict2).value_counts()
# esto se puede borrar no?
df['Host Response Time'].unique()
A continuación analizaremos los distintos Barrios en Madrid.
df['Final Neighbourhood'].describe()
df_grouped = pd.DataFrame(df.groupby(['Final Neighbourhood'])['Price'].agg(np.mean))
df_grouped.reset_index(inplace=True)
# plot this
df_grouped.sort_values(by=['Price'], ascending=True)\
.plot(kind='barh', x='Final Neighbourhood', y='Price',
figsize=(15,8), legend=False, color='skyblue')
plt.xlabel('\nMean Price\n', fontsize=12)
plt.ylabel('District\n', fontsize=12)
plt.title('Mean Prices by Neighbourhood\n', fontsize=14, fontweight='bold')
Como podemos observar en la tabla anterior, el barrio con los precios más altos de Airbnb es el barrio de Salamanca, donde la media llega a los 70 euros por noche, seguido del Centro. En el otro extremo tenemos Latina con un precio medio de 33 euros por noche, y por 36 euros por noche aproximadamente encontramos propiedades en Puente de Vallecas. Es relevante tener en cuenta que esto supone que una noche en un Airbnb en el barrio de Salamanca puede doblar en precio una propiedad que podamos encontrar en la Latina.
sns.set(style="whitegrid")
# Parameters
FILTER_PRICE_VALUE = 800
# Filter airbnb prices for shake of the visualization
sub_airbnb_price = df[df.Price < FILTER_PRICE_VALUE]
# Create ViolinPlot
fig, ax = plt.subplots(figsize=(18, 12))
density_neigh_price_plot = sns.violinplot(ax=ax, x="Final Neighbourhood", y="Price",
hue="Final Neighbourhood", data=sub_airbnb_price,
palette="plasma", dodge=False, legend=True)
density_neigh_price_plot.set(xlabel='Final Neighbourhood Group', ylabel='Price ($)',
title='Density and Distribution of prices for each Neighberhood Group')
ylabels = ['${}'.format(x) for x in density_neigh_price_plot.get_yticks()]
density_neigh_price_plot.set_yticklabels(ylabels)
# Ajustar posición de la leyenda
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.show()
En el gráfico anterior podemos diferenciar tres grupos según sus precios y barrio. En el precio más alto podríamos agrupar el barrio de Salamanca, cuyos precios se distribuyen en torno a los 40-90 euros por noche, a continuación el barrio de Retiro, cuyos valores son similares y también el barrio del Centro. En el segundo grupo podríamos agrupar los barrios de Chamartín, Moncloa-Aravaca y Chamberí. Sus precios se distribuyen en torno a los 40-75 euros por noche. Y en el último grupo encontraríamos los barrios de Tetúan, Arganzuela, Ciudad Lineal, Carabanchel, Puente de Vallecas y Latina. Sus precios varían en torno a los 25-60 euros por noche.
df.plot.scatter(x="Number of Reviews", y="Price", figsize=(12,7), c='skyblue')
plt.title('Relation between Number of Reviews & Price\n', fontsize=12, fontweight='bold');
El mayor número de Reviews lo encontramos en los apartamentos cuyo precio se encuentra desde el precio más bajo hasta los 100 euros por noche, a medida que aumenta el precio el número de reseñas disminuye. Esto se debe a que menos clientes se hospedan en propiedades cuyo precio supera los 100 o 125 euros por noche y además el número que encontramos de estos en Airbnb, también es menor.
En este apartado vamos a evaluar la relación entre el barrio y la disponibilidad de la propiedad. Para ello vamos a crear un boxplot que nos permita ver la distribución de la variable Availability 365 (durante todo el año) para cada uno de los barrios.
plt.figure(figsize=(20,8))
ax = sns.boxplot(data=df, x='Final Neighbourhood',y='Availability 365',palette='plasma')
plt.figure(figsize=(10,6))
sns.scatterplot(df.Longitude,df.Latitude,hue=df['Final Neighbourhood'])
plt.ioff()
Esta gráfica nos permite visualizar a nivel de longitud y latitud donde se sitúa cada barrio de Madrid, gracias a los diferentes colores podemos ver la división de barrios. Además podemos observar donde se aglomeran más alojamientos.
Por lo general, donde más alojamientos aglomerados encontramos es toda la zona central de Madrid: Centro, Chamberí, Retiro, Salamanca...
plt.figure(figsize=(10,6))
sns.scatterplot(df.Longitude,df.Latitude,hue=df['Room Type'])
plt.ioff()
A partir de la gráfica anterior y de esta última podemos observar la repartición geográfica de los tipos de habitación de los alojamientos disponibles. En la zona más central, en concreto en los barrios mencionados previamente, los alojamientos parecen ser principalmente apartamentos o casas enteras a alquilar. Cuanto más nos aljamos del centro, encontramos más habitaciones privadas y en general, encontramos pocas habitaciones compartidas en Madrid.
df['Availability 365'].value_counts()
plt.figure(figsize=(10,6))
sns.scatterplot(df.Longitude,df.Latitude,hue=df['Availability 365'])
plt.ioff()
Esta gráfica, nos muestra la disponibilidad 365 de los alojamientos también por su zona geográfica. Por lo general, la mayor parte de los alojamientos no presentan una disponibilidad 365 días del año. Sin embargo, si que hay alojamientos que presentan una disponibilidad bastante alta durante el año. En concreto en lo que parece ser el centro de Madrid, parece haber menos disponibilidad.
La siguiente celda de código calcula y muestra una matriz de correlación con un mapa de calor que representa visualmente las correlaciones entre las variables numéricas en el DataFrame df. El esquema de colores utilizado resalta las correlaciones más fuertes en tonos más intensos, mientras que las correlaciones más débiles se muestran en tonos más suaves.
# Calcula la matriz de correlación y aplica el estilo de gradiente de color
corr_matrix = df.corr()
styled_corr_matrix = corr_matrix.style.background_gradient(cmap='coolwarm')
# Configura el estilo de la tabla para reducir el tamaño
styled_corr_matrix.set_table_styles([{'selector': 'table', 'props': [('font-size', '10pt')]}])
# Muestra el gráfico
plt.figure(figsize=(18, 12))
sns.heatmap(corr_matrix, cmap='coolwarm', annot=True, fmt=".2f")
plt.title('Matriz de correlación')
plt.show()
Como podemos observar en la gráfica superior, las categorías que mayor correlación positiva tienen entre ellas son:
En la siguiente celda podemos observar las diferentes columnas del dataframe:
df.columns
Esta celda convierte variables categóricas en binarias y luego reemplaza las columnas originales del DataFrame original con estas nuevas variables binarias. Esto puede ser útil para realizar análisis o modelado utilizando algoritmos que requieren variables numéricas en lugar de categóricas.
property_type = pd.get_dummies(df['Property Type']).astype(int)
room_type= pd.get_dummies(df['Room Type']).astype(int)
bed_type= pd.get_dummies(df['Bed Type']).astype(int)
cancel_policy = pd.get_dummies(df['Cancellation Policy']).astype(int)
host_response_time = pd.get_dummies(df['Host Response Time']).astype(int)
#eliminamos las columnas originales y las sustituimos por las nuevas
df = df.drop(['Cancellation Policy', 'Property Type', 'Room Type', 'Bed Type', 'Host Response Time'], axis = 1)
df = pd.concat((df, cancel_policy, property_type, room_type, bed_type, host_response_time), axis = 1)
df2 = df
Eliminamos las colunmas City y Final Neighbourhood
df = df.drop(['City'], axis = 1)
df = df.drop(['Final Neighbourhood'], axis = 1)
Así es como quedaría el dataframe:
df.head()
Esta celda divide los datos en conjuntos de entrenamiento y prueba para su posterior uso en el modelado o análisis de datos.
# create target and features
a = df.drop(['Price'], axis = 1)
b = df.Price
# split our data
X_train, X_test, y_train, y_test = train_test_split(a, b, test_size=0.2, random_state=13)
Esta celda estandariza las características de los conjuntos de entrenamiento y prueba utilizando la técnica de escala estándar, lo que ayuda a asegurar que todas las características estén en la misma escala y tengan una distribución similar. Esto es comúnmente utilizado en algoritmos de machine learning que asumen que las características están normalmente distribuidas y tienen varianzas comparables.
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
La siguiente celda de código realiza una regresión lineal utilizando el modelo de regresión lineal de scikit-learn. Se ajusta el modelo a los datos de entrenamiento, se calcula el coeficiente de determinación R^2 para evaluar el rendimiento del modelo en el conjunto de prueba y se realizan predicciones tanto en el conjunto de prueba como en el conjunto de entrenamiento. Finalmente, se imprime en pantalla el valor del coeficiente de determinación R^2 obtenido.
# linear regression testing
reg = linear_model.LinearRegression()
reg.fit(X_train, y_train)
r2_lr = metrics.r2_score(y_test, reg.predict(X_test))
y_test_pred = reg.predict(X_test)
y_train_pred = reg.predict(X_train)
print (f"r2: {round(r2_lr, 5)}")
La siguiente celda, muestra dos scatter plots de la predicción de valores frente a los valores actuales, mostrando el test set y el train set.
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
fig.suptitle('Predicted vs. actual values', fontsize=14, y=1)
plt.subplots_adjust(top=0.93, wspace=0)
ax1.scatter(y_test, y_test_pred, s=2, alpha=0.7)
ax1.plot(list(range(2,8)), list(range(2,8)), color='black', linestyle='--')
ax1.set_title('Test set')
ax1.set_xlabel('Actual values')
ax1.set_ylabel('Predicted values')
ax2.scatter(y_train, y_train_pred, s=2, alpha=0.7)
ax2.plot(list(range(2,8)), list(range(2,8)), color='black', linestyle='--')
ax2.set_title('Train set')
ax2.set_xlabel('Actual values')
ax2.set_ylabel('')
ax2.set_yticklabels(labels='')
plt.show()
Las siguientes dos celdas ajustan un modelo de regresión lineal utilizando los datos de entrenamiento X_train y y_train.
lr = LinearRegression()
lr.fit(X_train,y_train)
lr.score(X_train, y_train)
lr.score(X_test,y_test)
El valor obtenido para lr.score(X_train, y_train) es de 0.5897608998381769, lo cual indica que el modelo explica aproximadamente el 58.98% de la variabilidad en los datos de entrenamiento. Por otro lado, el valor obtenido para lr.score(X_test, y_test) es de 0.594114931227745, lo cual indica que el modelo explica aproximadamente el 59.41% de la variabilidad en los datos de prueba.
Otra forma de hacerlo
data_train, data_test = train_test_split(df, test_size = 0.2)
X_train = data_train.drop(columns='Price')
y_train = data_train['Price']
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
lr = LinearRegression()
lr.fit(X_train_scaled,y_train)
lr.score(X_train_scaled,y_train)
X_test = data_test.drop(columns='Price')
y_test = data_test['Price']
X_test_scaled = scaler.transform(X_test)
predictions = lr.predict(X_test_scaled)
lr.score(X_test_scaled,y_test)
results_df = ( data_test
.loc[:,['Price']]
.assign(PricePredicted=predictions)
.assign(SquaredError=lambda x:(x.Price - x.PricePredicted)**2)
)
results_df
RMSE (Root Mean Squared Error) es una medida de la precisión de un modelo de regresión. se utiliza comúnmente para evaluar la calidad de las predicciones en problemas de regresión. Cuanto menor sea el valor del RMSE, mejor será la capacidad del modelo para ajustarse a los datos y realizar predicciones precisas.
rmse_1 = np.sqrt(results_df['SquaredError'].mean())
rmse_1
rmse_2 = mean_squared_error(results_df['Price'], results_df['PricePredicted'], squared=False)
rmse_2
El modelo de regresión lineal cuenta con un RMSE de 23.35 y un error medio de 38.1
# porcentaje de error medio #
ratio_error = round(rmse_2/results_df['Price'].mean()*100,2)
ratio_error
Vamos a utilizar el método de Árboles de Decisión, para el cual no es necesario utilizar los datos estandarizados. El método de árboles de decisión construye un modelo predictivo utilizando un enfoque basado en reglas de decisión para resolver problemas de clasificación y de regresión. Se basa en la idea de crear un modelo en forma de árbol, donde cada nodo interno representa una característica o atributo, cada rama representa una posible decisión o resultado y cada hoja representa una clase o valor de salida.
# using a decision tree regressor
# using a tree with max depth.
# it will provoque overfitting.
dtree = DecisionTreeRegressor()
dtree.fit(X_train, y_train)
dtree.score(X_train, y_train)
dtree.score(X_test, y_test)
dtree2 = DecisionTreeRegressor(max_depth=6)
dtree2.fit(X_train, y_train)
dtree2.score(X_train, y_train)
dtree2.score(X_test, y_test)
predictions_dtree = dtree2.predict(X_test)
Observamos los resultados:
results_dtree = ( data_test
.loc[:,['Price']]
.assign(PricePredicted=predictions_dtree)
.assign(SquaredError=lambda x:(x.Price - x.PricePredicted)**2)
)
results_dtree.head()
En la tabla anterior aparecen una serie de precios estimados por este algoritmo, observamos como mayoritariamente se aproximan al precio real, salvo en algunos casos donde el algoritmo ha fallado en la estimación.
rmse_3 = mean_squared_error(results_dtree['Price'], results_dtree['PricePredicted'], squared=False)
rmse_3
Arriba hemos calculado el error cuadrático medio, que es una medida de la diferencia entre los valores reales y los valores predichos. Un RMSE más bajo indica un mejor ajuste del modelo a los datos. En nuestro caso, el resultado del RMSE es de 21.37, lo que significa que, en promedio, las predicciones del modelo de árbol de decisión difieren de los valores reales en aproximadamente 21.37 unidades de la variable "Price". Un RMSE más bajo indicaría una mejor precisión en las predicciones, sin embargo, este valor es correcto y nuestro algoritmo predice solventemente los precios.
# porcentaje de error medio #
ratio_error_2 = round(rmse_3/results_dtree['Price'].mean()*100,2)
ratio_error_2
Seguidamente hemos calculado el porcentaje de error medio, que es una medida relativa que indica cuánto difieren en promedio las predicciones del modelo de árbol de decisión de los valores reales, expresado como un porcentaje del valor medio de la variable "Price". Un porcentaje de error medio más bajo indica un mejor ajuste y una mayor precisión del modelo. En nuestro caso, el resultado del porcentaje de error medio es de 35.6, lo que significa que, en promedio, las predicciones del modelo de árbol de decisión difieren del valor medio de la variable "Price" en aproximadamente el 35.6%.
En este apartado estimaremos el KNN, debemos utilizar los datos estandarizados. Se trata de un algoritmo de aprendizaje automático supervisado que se basa en la idea de que los ejemplos similares tienden a tener etiquetas similares.
# apply k-nn method
knn = KNeighborsRegressor(n_neighbors=5)
knn.fit(X_train_scaled, y_train)
knn.score(X_train_scaled, y_train)
knn.score(X_test_scaled, y_test)
# evaluate the model
predictions_knn = knn.predict(X_test_scaled)
Observamos los resultados:
# create a dataframe with the results
results_knn = ( data_test
.loc[:,['Price']]
.assign(PricePredicted=predictions_knn)
.assign(SquaredError=lambda x:(x.Price - x.PricePredicted)**2)
)
results_knn.head()
En este caso podemos observar como los precios predichos del modelo mediante KNN se aproximan mucho a los reales.
# mean squared error
rmse_4 = mean_squared_error(results_knn['Price'], results_knn['PricePredicted'], squared=False)
rmse_4
Hemos calculado de nuevo el error medio cuadrático y hemos obtenido un 22.14% que indica que nuestro modelo por Árboles de Decisión estima peor los precios que el modelo mediante KNN. Lo que significa que, en promedio, las predicciones del modelo de árbol de decisión difieren de los valores reales en aproximadamente 22.14 unidades de la variable "Price".
# porcentaje de error medio #
ratio_error_3 = round(rmse_4/results_knn['Price'].mean()*100,2)
ratio_error_3
A continuación haremos una tabla con los tres modelos para comparar los resultados:
models = ['Linear Regression', 'Decision Tree', 'K-nn']
rmse = [rmse_1, rmse_3, rmse_4]
ratio_error_vec = [ratio_error, ratio_error_2, ratio_error_3]
summary_df = pd.DataFrame({'Model':models, 'RMSE':rmse, 'Ratio Error':ratio_error_vec}).round(2)
summary_df.head()
Por ahora el modelo que mejor predice el precio es KNN, con un RMSE de 22.15 y un ratio error de 36.13
# Modeling using random forest
rf = RandomForestRegressor(n_estimators=200, max_depth=10, random_state=0, )
rf.fit(X_train, y_train)
rf.score(X_train, y_train)
El resultado obtenido es 0.8228967765649808, lo que significa que el modelo de Random Forest ajustado explica aproximadamente el 82.28% de la varianza en los datos de entrenamiento. Esto indica un buen ajuste del modelo en relación a los datos utilizados para entrenarlo.
rmse_5 = mean_squared_error(y_test, rf.predict(X_test), squared=False)
ratio_error_5 = round(rmse_5/y_test.mean()*100,2)
summary_df = summary_df.append({'Model':'Random Forest', 'RMSE':rmse_5, 'Ratio Error':ratio_error_5}, ignore_index=True)
summary_df
Tras haber estimado mediante Random Forest, deducimos que éste es el mejor estimador por ahora. Con un RMSE de 19.63 y un ratio error de 32.03
El Gradient Boosting se trata de una técnica de ensamblado que combina múltiples modelos de aprendizaje débiles para formar un modelo más fuerte y preciso. A diferencia de otros algoritmos de ensamblado, como el Random Forest, que construyen múltiples árboles de decisión independientes, el Gradient Boosting construye los árboles de decisión de forma secuencial. Cada nuevo árbol se ajusta a los errores residuales del modelo anterior, intentando corregir las deficiencias del modelo anterior. De esta manera, el modelo final es una combinación ponderada de múltiples árboles, donde cada árbol se ajusta para mejorar la precisión del modelo en los casos en los que los modelos anteriores fallaron.
'''Gradient Boosted Regressor'''
GBoost = GradientBoostingRegressor(n_estimators=3000, learning_rate=0.01)
GBoost.fit(X_train,y_train)
'''Get Predictions & Metrics'''
predicts2 = GBoost.predict(X_test)
print("""
Mean Squared Error: {}
R2 Score: {}
Mean Absolute Error: {}
""".format(
np.sqrt(metrics.mean_squared_error(y_test, predicts2)),
r2_score(y_test,predicts2) * 100,
mean_absolute_error(y_test,predicts2)
))
Observamos los resultados:
result_Gboost = pd.DataFrame({
'Actual Values': np.array(y_test).flatten(),
'Predicted Values': predicts2.flatten()}).head(20)
result_Gboost.head(5)
rmse_6 = mean_squared_error(result_Gboost['Actual Values'], result_Gboost['Predicted Values'], squared=False)
rmse_6
ratio_error_5 = round(rmse_6/result_Gboost['Actual Values'].mean()*100,2)
ratio_error_5
summary_df = summary_df.append({'Model':'Gradient Boost', 'RMSE':rmse_6, 'Ratio Error':ratio_error_5}, ignore_index=True)
summary_df
El modelo estimado mediante Gradient Boost muestro los mejores resultados con un RMSE de tan solo un 21.64 y un ratio error de 31.06.
title=['Pred vs Actual']
fig = go.Figure(data=[
go.Bar(name='Predicted', x=error_airbnb.index, y=error_airbnb['Predicted Values']),
go.Bar(name='Actual', x=error_airbnb.index, y=error_airbnb['Actual Values'])
])
fig.update_layout(barmode='group')
fig.show()
plt.figure(figsize=(16,8))
sns.regplot(predicts2,y_test)
plt.xlabel('Predictions')
plt.ylabel('Actual')
plt.title("Gradient Boosted Regressor model Predictions")
plt.show()
# Crear un modelo de regresión de árbol de decisión para el bagging
base_model = DecisionTreeRegressor()
# Crear el modelo de bagging
bagging_model = BaggingRegressor(base_estimator=base_model, n_estimators=10, random_state=42)
# Entrenar el modelo de bagging
bagging_model.fit(X_train, y_train)
# Predecir las etiquetas en el conjunto de prueba
y_pred = np.around(bagging_model.predict(X_test),2)
# Calcular el error absoluto medio
mae = mean_absolute_error(y_test, y_pred)
print("Error absoluto medio: {:.2f}".format(mae))
# Calcular el error cuadrático medio
mse = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio: {:.2f}".format(mse))
El valor del error absoluto medio obtenido es de 13.74. Esto indica que, en promedio, las predicciones del modelo tienen una diferencia absoluta de 13.74 unidades respecto a los valores reales en el conjunto de prueba. El valor del error cuadrático medio obtenido es de 388.52. El MSE es una medida de la calidad de las predicciones al calcular el promedio de los errores al cuadrado. Cuanto menor sea su valor, mejor será el rendimiento del modelo en términos de la precisión de las predicciones.
predicciones_bagging = pd.DataFrame({
'Actual Values': np.array(y_test).flatten(),
'Predicted Values': y_pred.flatten()}).head(20)
predicciones_bagging.head(5)
rmse_7 = mean_squared_error(predicciones_bagging['Actual Values'], predicciones_bagging['Predicted Values'], squared=False)
rmse_7
El modelo bagging cuenta con un RMSE de 24.82 y un error medio de 35.61
ratio_error_6 = round(rmse_7/predicciones_bagging['Actual Values'].mean()*100,2)
ratio_error_6
summary_df = summary_df.append({'Model':'Bagging', 'RMSE':rmse_7, 'Ratio Error':ratio_error_6}, ignore_index=True)
summary_df
La siguiente celda de código ajusta un modelo de regresión mediante el algoritmo de Gradient Boosting. Se utiliza un modelo con 100 estimadores (árboles de decisión), una profundidad máxima de 3, una tasa de aprendizaje de 0.1 y una semilla aleatoria de 42.
boosting_model = GradientBoostingRegressor(n_estimators=100, max_depth=3, learning_rate=0.1, random_state=42)
# Entrenar el modelo de boosting
boosting_model.fit(X_train, y_train)
# Predecir las etiquetas en el conjunto de prueba
y_pred = boosting_model.predict(X_test).round(2)
# Agregar las predicciones a la tabla de prueba
X_test['precio_boosting'] = y_pred
# Calcular el error absoluto medio
mae = mean_absolute_error(y_test, y_pred)
print("Error absoluto medio: {:.2f}".format(mae))
# Calcular el error cuadrático medio
mse = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio: {:.2f}".format(mse))
El error absoluto medio de 13.56 indica que, en promedio, las predicciones del modelo difieren en 13.56 unidades del valor real. El error cuadrático medio de 365.63 representa la media de los errores al cuadrado, lo cual indica que el modelo tiene una variabilidad pronunciada en la precisión de las predicciones. En general, se busca minimizar tanto el error absoluto medio como el error cuadrático medio para obtener un modelo más preciso.
predicciones_boosting = pd.DataFrame({
'Actual Values': np.array(y_test).flatten(),
'Predicted Values': y_pred.flatten()}).head(20)
predicciones_boosting.head(5)
rmse_8 = mean_squared_error(predicciones_boosting['Actual Values'], predicciones_boosting['Predicted Values'], squared=False)
rmse_8
El modelo boosting cuenta con un RMSE de 24.27 y un error medio de 34.83
ratio_error_7 = round(rmse_8/predicciones_boosting['Actual Values'].mean()*100,2)
ratio_error_7
summary_df = summary_df.append({'Model':'Boosting', 'RMSE':rmse_8, 'Ratio Error':ratio_error_7}, ignore_index=True)
summary_df
# Definir el número de clusters
k = 3
# KMeans
km = KMeans(n_clusters=k, random_state=42)
km_labels = km.fit_predict(df)
# Agglomerative Clustering
ac = AgglomerativeClustering(n_clusters=k)
ac_labels = ac.fit_predict(df)
# Birch
birch = Birch(n_clusters=k)
birch_labels = birch.fit_predict(df)
# Spectral Clustering
sc = SpectralClustering(n_clusters=k, affinity='nearest_neighbors')
sc_labels = sc.fit_predict(df)
# Calcular el puntaje de silueta para evaluar los resultados del clustering
print(f"KMeans Silhouette Score: {silhouette_score(df, km_labels)}")
print(f"Agglomerative Clustering Silhouette Score: {silhouette_score(df, ac_labels)}")
print(f"Birch Silhouette Score: {silhouette_score(df, birch_labels)}")
print(f"Spectral Clustering Silhouette Score: {silhouette_score(df, sc_labels)}")
El Silhouette Score de un Cluster varía entre -1 y 1, donde un valor más cercano a 1 indica una mejor calidad de clustering. En nuestro caso, los resultados muestran el Silhouette Score obtenido para diferentes algoritmos de clustering aplicados a tus datos. KMeans Silhouette Score: 0.9938465467134879: indica que el algoritmo K-means ha producido clusters con una alta cohesión interna y una buena separación entre los clusters. Un valor cercano a 1 indica que los objetos dentro de los clusters son muy similares entre sí y están bien separados de los objetos de otros clusters.